package agents;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.FSMBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.WakerBehaviour;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.Property;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import model.Artifact;
import model.IdentificationProtocol;
import model.TourObject;
import model.ContentDTO;
import behaviours.RegisterServiceBehaviour;
import behaviours.SubscribeAndListenBehaviour;
import behaviours.Subscriber;

public class CuratorAgent extends Agent implements Subscriber {

	private ArrayList<Artifact> artifacts;
	private ArrayList<AID> participants;

	protected void setup() {
		System.out.println(getAID() + " running setup.");

		artifacts = initArtifacts();
		participants = new ArrayList<AID>();

		// Register service
		ServiceDescription sd1 = new ServiceDescription();
		sd1.setType("artifact-provider");
		sd1.setName("providercurator");
		sd1.addProperties(new Property("List of interests", null));
		ServiceDescription sd2 = new ServiceDescription();
		sd2.setType("artifact-informer");
		sd2.setName("informercurator");
		sd2.addProperties(new Property("Tour object", null));
		ServiceDescription[] sds = new ServiceDescription[2];
		sds[0] = sd1;
		sds[1] = sd2;

		addBehaviour(new RegisterServiceBehaviour(getAID(), sds));

		// ------------------------- TASK #1 -------------------------
		// Listen to requests from both tour guide or profiler
		// addBehaviour(new ListenToArtifactsRequestsBehaviour());

		// ------------------------- TASK #2 -------------------------
		// Run an auction

		// Subscribe to curators
		String[] providers = { "auction-participant" };
		addBehaviour(new SubscribeAndListenBehaviour(this, providers));

		addBehaviour(new WakerBehaviour(this, 100) {
			@Override
			protected void onWake() {
				addBehaviour(new RunAuctionBehavoiur(artifacts.get(0), 25000,
						15000));
			}
		});

	}

	private class RunAuctionBehavoiur extends FSMBehaviour {

		private ArrayList<ACLMessage> responses;
		private Artifact saleItem;
		private int price;
		private int minimumprice;

		private final int BUYER_NOT_FOUND = 0;
		private final int BUYER_FOUND = 1;
		private final int MINIMUM_NOT_REACHED = 0;
		private final int MINIMUM_REACHED = 1;

		private final String STATE_AUCTION_START = "AUCTION_START";
		private final String STATE_SEND_PROPOSAL = "SEND_PROPOSAL";
		private final String STATE_LISTEN_TO_BIDDING = "LISTEN_TO_BIDDING";
		private final String STATE_AUCTION_CLOSED = "AUCTION_CLOSED";

		public RunAuctionBehavoiur(Artifact item, int marketvalue,
				int minimumprice) {
			price = (int) (marketvalue * 1.5);
			this.minimumprice = minimumprice;
			saleItem = item;
			responses = new ArrayList<ACLMessage>();

			registerFirstState(new AuctionStartBehaviour(), STATE_AUCTION_START);
			registerState(new SendProposalBehaviour(), STATE_SEND_PROPOSAL);
			registerState(new ListenToBiddingAndRespondBehaviour(),
					STATE_LISTEN_TO_BIDDING);
			registerLastState(new CloseAuctionBehaviour(), STATE_AUCTION_CLOSED);

			registerDefaultTransition(STATE_AUCTION_START, STATE_SEND_PROPOSAL);
			registerTransition(STATE_SEND_PROPOSAL, STATE_LISTEN_TO_BIDDING,
					MINIMUM_NOT_REACHED);
			registerTransition(STATE_SEND_PROPOSAL, STATE_AUCTION_CLOSED,
					MINIMUM_REACHED);
			registerTransition(STATE_LISTEN_TO_BIDDING, STATE_SEND_PROPOSAL,
					BUYER_NOT_FOUND);
			registerTransition(STATE_LISTEN_TO_BIDDING, STATE_AUCTION_CLOSED,
					BUYER_FOUND);

			scheduleFirst();
		}

		private class AuctionStartBehaviour extends OneShotBehaviour {

			@Override
			public void action() {
				ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
				try {
					msg.setContentObject(saleItem);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					for (AID aid : participants)
						msg.addReceiver(aid);
					myAgent.send(msg);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		private class SendProposalBehaviour extends OneShotBehaviour {

			private boolean minimumreached = false;

			@Override
			public void action() {
				price = (int) (price * 0.9);
				if (price >= minimumprice) {
					
					System.out.println("New offer to send to participants: " + price);
					
					ACLMessage msg = new ACLMessage(ACLMessage.CFP);
					try {
						msg.setContentObject(price);
					} catch (IOException e) {
						e.printStackTrace();
					}
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					for (AID aid : participants)
						msg.addReceiver(aid);
					myAgent.send(msg);
				} else {
					minimumreached = true;
				}
			}

			@Override
			public int onEnd() {
				if (!minimumreached) {
					System.out
							.println("Minimum value was not reached, continuing auction");
					return MINIMUM_NOT_REACHED;
				} else {
					System.out
							.println("Minimum value was reached, closing auction.");
					return MINIMUM_REACHED;
				}
			}
		}

		private class ListenToBiddingAndRespondBehaviour extends Behaviour {

			public ListenToBiddingAndRespondBehaviour() {
				responses.clear();
			}

			@Override
			public void action() {
				ACLMessage msg = myAgent
						.receive(MessageTemplate
								.MatchProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION));
				if (msg != null) {
					responses.add(msg);
				} else
					block();
			}

			@Override
			public boolean done() {
				return responses.size() == participants.size();
			}

			@Override
			public int onEnd() {
				
				ArrayList<AID> buyers = new ArrayList<AID>();
				
				for (ACLMessage msg : responses) {
					if (msg.getPerformative() == ACLMessage.PROPOSE) {
						if (msg.getContent().equals("ACCEPT")) {
							buyers.add(msg.getSender());
						}
					}
				}
				
				// Clear the response list
				responses.clear();
				
				System.out.println("Number of buyers found this bidding round: " + buyers.size());
				
				if (buyers.size() > 0) {
					// winner is index 0 of buyers
					ACLMessage msg = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					msg.addReceiver(buyers.get(0));
					myAgent.send(msg);

					// all other in the array will receive a reject
					msg = new ACLMessage(ACLMessage.REJECT_PROPOSAL);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					for (int i = 1; i < buyers.size(); i++) {
						msg.addReceiver(buyers.get(i));
					}
					myAgent.send(msg);
					System.out
							.println("Curator found a buyer for the artifact");
					return BUYER_FOUND;
				} else {
					System.out
							.println("Curator did not find a buyer for the artifact");
					return BUYER_NOT_FOUND;
				}
			}
		}

		private class CloseAuctionBehaviour extends OneShotBehaviour {

			@Override
			public void action() {
				ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
				msg.setContent("AUCTION_CLOSED");
				msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
				for (AID aid : participants)
					msg.addReceiver(aid);
				myAgent.send(msg);
			}
		}
	}

	@SuppressWarnings("unused")
	private class ListenToArtifactsRequestsBehaviour extends CyclicBehaviour {

		@Override
		public void action() {
			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchPerformative(ACLMessage.REQUEST));
			if (msg != null) {
				try {
					// Fetch our ContentDTO
					Object obj = msg.getContentObject();
					ContentDTO content = null;

					// Make it was a ContentDTO
					if (obj instanceof ContentDTO)
						content = (ContentDTO) obj;

					// Only perform this if we have proper content
					if (content != null) {

						// If the sender was a tour guide
						if (content.getSender().equals(
								IdentificationProtocol.SENDER_WAS_TOUR_GUIDE)) {
							// This contains a list of interests
							@SuppressWarnings("unchecked")
							ArrayList<String> interestTags = (ArrayList<String>) content
									.getContent();

							// Generate tour object and send reply to your agent
							myAgent.addBehaviour(new GenerateTourAndReplyBehaviour(
									interestTags, msg.getSender()));
						}

						// If the sender was a profiler
						else if (content.getSender().equals(
								IdentificationProtocol.SENDER_WAS_PROFILER)) {
							if (content.getContent() instanceof TourObject)
								myAgent.addBehaviour(new FindAndReturnArtifactBehaviour(
										(TourObject) content.getContent(), msg
												.getSender()));
						}

						// If the sender was unknown
						else {
							System.out.println("Unknown sender!");
						}
					}
				} catch (UnreadableException e) {
					e.printStackTrace();
				}
			} else
				block();
		}
	}

	private class FindAndReturnArtifactBehaviour extends OneShotBehaviour {

		TourObject tourObject;
		AID requester;

		public FindAndReturnArtifactBehaviour(TourObject obj, AID requester) {
			this.tourObject = obj;
			this.requester = requester;
		}

		@Override
		public void action() {
			// Find the corresponding artifact
			// Send it to the profiler

			Artifact artifactToReturn = null;
			for (Artifact art : artifacts) {
				if (tourObject.getId() == art.getId()) {
					artifactToReturn = art;
				}
			}

			if (artifactToReturn != null) {
				// Reply to tour agent
				ACLMessage msg = new ACLMessage(ACLMessage.CONFIRM);
				msg.addReceiver(this.requester);
				try {
					msg.setContentObject(new ContentDTO(artifactToReturn,
							IdentificationProtocol.SENDER_WAS_CURATOR));
				} catch (IOException e) {
					e.printStackTrace();
				}
				send(msg);
			}
		}
	}

	private class GenerateTourAndReplyBehaviour extends OneShotBehaviour {

		private ArrayList<String> interestTags;
		private AID sender;

		public GenerateTourAndReplyBehaviour(ArrayList<String> interestTags,
				AID sender) {
			this.interestTags = interestTags;
			this.sender = sender;
		}

		@Override
		public void action() {
			// Generate tour
			ArrayList<Artifact> tour = findArtifacts(interestTags);

			// Reply to tour agent
			ACLMessage msg = new ACLMessage(ACLMessage.CONFIRM);
			msg.addReceiver(sender);
			try {
				msg.setContentObject(new ContentDTO(tour,
						IdentificationProtocol.SENDER_WAS_CURATOR));
			} catch (IOException e) {
				e.printStackTrace();
			}
			send(msg);
		}

		private ArrayList<Artifact> findArtifacts(ArrayList<String> interestTags) {

			ArrayList<Artifact> relevantArtifacts = new ArrayList<Artifact>();

			HashMap<Artifact, Boolean> scoreBoard = new HashMap<Artifact, Boolean>();
			for (Artifact art : artifacts) {
				scoreBoard.put(art, false);
			}

			// For every interest tag received
			for (String tag : interestTags) {
				// Check every artifact
				for (Artifact art : artifacts) {
					// Find the current score for the artifact
					boolean currentRelevance = scoreBoard.get(art);

					// Check name
					if (art.getName().equals(tag))
						currentRelevance = true;

					// Check for every genre
					for (String genre : art.getGenres()) {
						if (genre.equals(tag))
							currentRelevance = true;
					}

					// Check creator
					if (art.getCreator().equals(tag))
						currentRelevance = true;

					try {
						// Check year of creation
						if (art.getYearOfCreation() == Integer.parseInt(tag))
							currentRelevance = true;
					} catch (NumberFormatException e) {
						// If this happens, the tag was not a year of creation
						// and we don't do anything about it
					}

					// Check place of creation
					if (art.getPlaceOfCreation().equals(tag))
						currentRelevance = true;

					// Update the scoreboard
					scoreBoard.put(art, currentRelevance);
				}
			}

			for (Artifact art : artifacts) {
				Boolean interesting = scoreBoard.remove(art);
				if (interesting)
					relevantArtifacts.add(art);
			}

			return relevantArtifacts;
		}
	}

	private ArrayList<Artifact> initArtifacts() {

		ArrayList<Artifact> artifacts = new ArrayList<Artifact>();

		String[] genre1 = { "Impressionalism", "Portrait", "Painting" };
		// String[] genre2 = { "Baroque", "Renaissance", "Painting" };
		// String[] genre3 = { "Renaissance", "Painting" };
		// String[] genre4 = { "Gothic", "Renaissance", "Sculpture" };
		String[] genre5 = { "Expressionalism", "Gothic", "Painting" };
		String[] genre6 = { "Antique", "Sculpture", "Bronze" };
		String[] genre7 = { "Renaissance", "Sculpture", "Marble" };

		artifacts.add(new Artifact(0, "Mona Lisa", "Leonardo da Vinci", 1519,
				"Italy", genre1));

		artifacts.add(new Artifact(1, "The Scream", "Edvard Munch", 1910,
				"Norway", genre5));

		artifacts.add(new Artifact(2, "The Thinker", "Auguste Rodin", 1902,
				"France", genre6));

		artifacts.add(new Artifact(3, "The Statue of David",
				"Michelangelo di Lodovico Buonarroti Simoni", 1504, "Italy",
				genre7));

		return artifacts;

	}

	protected void takeDown() {
		System.out.println("takeDown()");
	}

	@Override
	public void updateSubscription(String type, AID name) {
		System.out.println("updateSubscription ran on curator agent");
		if (type.equals("auction-participant")) {
			participants.add(name);
			System.out.println("CuratorAgent added " + name
					+ " to auction participants.");
		}
	}
}
